跳到主要内容

Canvas 绘图学习

转载自 MDN - Canvas教程(少部分改动)

什么是 canvas?

HTML5 <canvas> 元素是一个承载绘图的容器,通过 JavaScript 来完成。

<canvas id="tutorial" width="150" height="150"></canvas>

<canvas> 标签只有两个属性 width 和 height

注意,当没有设置宽度和高度的时候,canvas 会初始化宽度为 300 像素和高度为 150 像素。该元素可以使用 CSS 来定义大小,但在绘制时图像会伸缩以适应它的框架尺寸:如果 CSS 的尺寸与初始画布的 比例不一致,它会出现扭曲

替换内容

由于某些较老的浏览器(尤其是 IE9 之前的 IE 浏览器)或者文本浏览器不支持 HTML 元素 "canvas",在这些浏览器上应该总是能展示替代内容。

可以在 <canvas> 标签中 提供了替换内容。不支持 <canvas> 的浏览器将会忽略容器并渲染 <canvas> 里面的内容。而支持 <canvas> 的浏览器将会忽略在容器中包含的内容,正常渲染 canvas。

举个例子,我们可以提供对 canvas 内容的文字描述或者是提供动态生成内容相对应的静态图片,如下所示:

<canvas id="stockGraph" width="150" height="150">
当前浏览器不支持 canvas!请升级浏览器
</canvas>

<canvas id="clock" width="150" height="150">
<img src="images/clock.png" width="150" height="150" alt=""/>
</canvas>

检测是否支持

替换内容是用于在不支持 <canvas> 标签的浏览器中展示的。通过简单的测试 getContext() 方法的存在,脚本可以检查编程支持性

var canvas = document.getElementById('tutorial');

if (canvas.getContext){
var ctx = canvas.getContext('2d');
// drawing code here
} else {
// canvas-unsupported code here
}

渲染上下文

<canvas> 元素创造了一个固定大小的画布,它公开了一个或多个渲染上下文(3D 和 2D),其可以用来绘制和处理要展示的内容。这里主要说 2D渲染上下文(3D 的上下文使用的是就是 WebGL 了)

首先脚本需要找到渲染上下文,然后在它的上面绘制。<canvas> 元素有一个叫做 getContext() 的方法,这个方法是用来获得渲染上下文和它的绘画功能。

// 首先获取节点
var canvas = document.getElementById('tutorial');
// getContext() 只有一个参数,上下文的格式(2d or 3d)
var ctx = canvas.getContext('2d');

模板骨架

为了简洁, 在 HTML 中内嵌了 script 元素, 但并不推荐这种做法。

<html>
<head>
<title>Canvas tutorial</title>
<script type="text/javascript">
function draw(){
var canvas = document.getElementById('tutorial');
if (canvas.getContext){
var ctx = canvas.getContext("2d");

ctx.fillStyle = "rgb(200,0,0)";
ctx.fillRect (10, 10, 55, 50);

ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
ctx.fillRect (30, 30, 55, 50);
}
}
</script>
<style type="text/css">
canvas { border: 1px solid black; }
</style>
</head>
<body onload="draw();">
<canvas id="tutorial" width="150" height="150"></canvas>
</body>
</html>

上面的脚本中包含一个叫做 draw() 的函数,当页面加载结束的时候就会执行这个函数,它绘制了两个长方形,其中的一个有着alpha透明度。

或者使用监听事件,监听 load 事件

window.addEventListener('load', init, false)

绘制图形

栅格的概念

画布栅格(canvas grid)以及坐标空间。

上一节中的 HTML 模板中有个宽 150px, 高 150px的 canvas 元素。如下图所示,canvas 元素默认被网格所覆盖。通常来说网格中的一个单元相当于 canvas 元素中的一像素。

栅格的起点为左上角,坐标为(0,0)。所有元素的位置都相对于原点定位。所以图中蓝色方形左上角的坐标为距离左边(X轴)x像素,距离上边(Y轴)y像素(坐标为(x,y))。

绘制矩形

<canvas> 只支持两种形式的图形绘制:矩形和路径(由一系列点连成的线段)。所有其他类型的图形都是通过一条或者多条路径组合而成的。不过,我们拥有众多路径生成的方法让复杂图形的绘制成为了可能。

canvas提供了三种方法绘制矩形:

// 绘制一个填充的矩形
fillRect(x, y, width, height)

// 绘制一个矩形的边框
strokeRect(x, y, width, height)

// 清除指定矩形区域,让清除部分完全透明。
clearRect(x, y, width, height)

上面提供的方法之中每一个都包含了相同的参数。x 与 y 指定了在 canvas 画布上所绘制的矩形的左上角(相对于原点)的坐标。 width 和 height 设置矩形的尺寸。

如下所示

function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');

ctx.fillRect(25, 25, 100, 100);
ctx.clearRect(45, 45, 60, 60);
ctx.strokeRect(50, 50, 50, 50);
}
}

fillRect() 函数绘制了一个边长为 100px的黑色正方形。clearRect() 函数从正方形的中心开始擦除了一个 60 * 60px 的正方形,接着 strokeRect() 在清除区域内生成一个 50 * 50 的正方形边框。

绘制路径的概念

图形的基本元素是路径。路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合。一个路径,甚至一个子路径,都是闭合的。使用路径绘制图形需要一些额外的步骤。

  1. 首先,需要创建路径起始点。
  2. 然后使用画图命令去画出路径。
  3. 之后把路径封闭。
  4. 一旦路径生成,就能通过描边或填充路径区域来渲染图形。

以下是所要用到的函数:

// 新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径。
beginPath()

// 闭合路径之后图形绘制命令又重新指向到上下文中。
closePath()
// 注意 closePath 并不是一个与beginPath对照使用的方法,其作用是绘制闭合路径

// 通过线条来绘制图形轮廓。
stroke()

// 通过填充路径的内容区域生成实心的图形。
fill()

1、生成路径的第一步叫做 beginPath()。本质上,路径是由很多子路径构成,这些子路径都是在一个列表中,所有的子路径(线、弧形、等等)构成图形。而每次这个方法调用之后,列表清空重置,然后我们就可以重新绘制新的图形。

注意:当前路径为空,即调用 beginPath() 之后,或者 canvas 刚建的时候,第一条路径构造命令通常是 moveTo() 用于指定起始位置。

它主要的作用是说明开始一段新的路径,重新作画,否则原有的路径参数会被带到这个路径上,比如说画两个正方形,如果第一个设置了参数为红色,那么如果不 beginPath(),第二个也会变红色。所以为了不影响,重新画新的路径必须说明。

另外 closePath() 也不是结束路径,而是关闭路径,它是将当前路径的起点和终点试图连线关闭,但这并不意味原先的路径结束了。而不重新开始路径意味着一直在用那一条路径作画,即使画的是闭合图形也是。这里可以自己用代码举例论证。

2、第二步就是调用函数指定绘制路径。

3、第三,就是闭合路径 closePath(),它不是必需的。这个方法会通过绘制一条从当前点到开始点的直线来闭合图形。如果图形是已经闭合了的,即当前点为开始点,该函数什么也不做。

注意:当你调用 fill() 函数时,所有没有闭合的形状都会自动闭合,所以你不需要调用 closePath() 函数。但是调用 stroke() 时不会自动闭合。

例如绘制一个三角形

function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');

ctx.beginPath();
ctx.moveTo(75, 50);
// 线宽设置,必须放在绘制之前
ctx.lineWidth = 2;
ctx.lineTo(100, 75);
ctx.lineTo(100, 25);
ctx.fill();
}
}

移动笔触

// 将笔触移动到指定的坐标x以及y上
moveTo(x, y)

当 canvas 初始化或者 beginPath() 调用后,通常会使用 moveTo() 函数设置起点。

也能够使用 moveTo() 绘制一些不连续的路径(例如绘画路径里面的东西)

function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var ctx = canvas.getContext('2d');

ctx.beginPath();
ctx.arc(75, 75, 50, 0, Math.PI * 2, true); // 绘制
ctx.moveTo(110, 75);
ctx.arc(75, 75, 35, 0, Math.PI, false); // 口(顺时针)
ctx.moveTo(65, 65);
ctx.arc(60, 65, 5, 0, Math.PI * 2, true); // 左眼
ctx.moveTo(95, 65);
ctx.arc(90, 65, 5, 0, Math.PI * 2, true); // 右眼
ctx.stroke();
}
}

绘制线

// 绘制一条从当前位置到指定x以及y位置的直线。
lineTo(x, y)

下面的例子绘制两个三角形,一个是填充的,另一个是描边的。

function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var ctx = canvas.getContext('2d');

// 填充三角形
ctx.beginPath();
ctx.moveTo(25, 25);
ctx.lineTo(105, 25);
ctx.lineTo(25, 105);
ctx.fill();

// 描边三角形
ctx.beginPath(); // 使用了 beginPath 清空之前的路径
ctx.moveTo(125, 125);
ctx.lineTo(125, 45);
ctx.lineTo(45, 125);
ctx.closePath();
ctx.stroke();
}
}

这里从调用 beginPath() 函数准备绘制一个新的形状路径开始。然后使用 moveTo() 函数移动到目标位置上。然后下面,两条线段绘制后构成三角形的两条边。

绘制圆弧

// 画一个以(x,y)为圆心的以 radius 为半径的圆弧(圆)
// 从 startAngle 开始到 endAngle 结束,
// 按照 anticlockwise 给定的方向(默认为顺时针)来生成。
arc(x, y, radius, startAngle, endAngle, anticlockwise)


// 根据给定的控制点和半径画一段圆弧,再以直线连接两个控制点
arcTo(x1, y1, x2, y2, radius)
  • arc 方法有六个参数:
  • x,y 为绘制圆弧所在圆上的圆心坐标。
  • radius 为半径。
  • startAngle 以及 endAngle 参数用弧度定义了开始以及结束的弧度。(这些都是以x轴为基准)。
  • anticlockwise 为一个布尔值。为 true 时,是逆时针方向,否则顺时针方向。

注意:arc() 函数中表示角的单位是弧度,不是角度。角度与弧度的js表达式: 弧度 = ( Math.PI / 180 ) * 角度。

使用例:绘制 12 个弧形

function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var ctx = canvas.getContext('2d');

for(var i = 0; i < 4; i++){
for(var j = 0; j < 3; j++){
ctx.beginPath();
var x = 25 + j * 50; // x 坐标值
var y = 25 + i * 50; // y 坐标值
var radius = 20; // 圆弧半径
var startAngle = 0; // 开始点
var endAngle = Math.PI + (Math.PI * j) / 2; // 结束点
var anticlockwise = i % 2 == 0 ? false : true; // 顺时针或逆时针

ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);

if (i>1){
ctx.fill();
} else {
ctx.stroke();
}
}
}
}
}

Path2D 对象

Path2D 对象用来缓存或记录绘画命令,这样能快速地回顾路径。

new Path2D();     // 空的Path对象
new Path2D(path); // 克隆Path对象
new Path2D(d); // 从SVG建立Path对象

Path2D API 添加了 addPath作为将path结合起来的方法

// 添加了一条路径到当前路径(可能添加了一个变换矩阵)。
Path2D.addPath(path [, transform])

使用例:创造了一个矩形和一个圆。它们都被存为Path2D对象

function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var ctx = canvas.getContext('2d');

var rectangle = new Path2D();
rectangle.rect(10, 10, 50, 50);

var circle = new Path2D();
circle.moveTo(125, 35);
circle.arc(100, 35, 25, 0, 2 * Math.PI);

ctx.stroke(rectangle);
ctx.fill(circle);
}
}

绘制线条的时候卡顿的问题

在 move 事件中只使用

ctx.lineTo(e.clientX, e.clientY - 85);  
ctx.stroke();

那么就有问题,因为 stroke() 会对这条 line 的所有轨迹做填充。就是他画了1万个点,那么就填充1万条line。为了兼顾效率,可以设置一个阈值,到了阈值之后就 beginPath,开一条新的 line。

document.addEventListener("pointermove", e => {  
if (!paint) return;
e.preventDefault();
e.stopPropagation();

ctx.lineTo(e.clientX, e.clientY - 85);
ctx.stroke();
if (points++ > 100) {
ctx.beginPath();
ctx.moveTo(e.clientX, e.clientY - 85)
points = 0;
}
})

使用样式和颜色

fillStyle 和 strokeStyle

// 设置图形的填充颜色。
fillStyle = color

// 设置图形轮廓的颜色。
strokeStyle = color

一旦设置了 strokeStyle 或者 fillStyle 的值,那么这个新值就会成为新绘制的图形的默认值。如果要给每个图形上不同的颜色,你需要重新设置 fillStyle 或 strokeStyle 的值。

// 这些 fillStyle 的值均为 '橙色'
ctx.fillStyle = "orange";
ctx.fillStyle = "#FFA500";
ctx.fillStyle = "rgb(255,165,0)";
ctx.fillStyle = "rgba(255,165,0,1)";

用了两个变量 i 和 j 来为每一个方格产生唯一的 RGB 色彩值,其中仅修改红色和绿色通道的值,而保持蓝色通道的值不变。

function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
for (var i=0;i<6;i++){
for (var j=0;j<6;j++){
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
Math.floor(255-42.5*j) + ',0)';
ctx.fillRect(j*25,i*25,25,25);
}
}
}

透明度 Transparency

// 属性影响到 canvas 里所有图形的透明度,有效的值范围是 0.0 (完全透明)到 1.0(完全不透明),默认是 1.0。
globalAlpha = transparencyValue

不过一般还是通过 RGBA 来修改不透明度

// 指定透明颜色,用于描边和填充样式
ctx.strokeStyle = "rgba(255,0,0,0.5)";
ctx.fillStyle = "rgba(255,0,0,0.5)";

在这个例子里,将用四色格作为背景,设置 globalAlpha 为 0.2 后,在上面画一系列半径递增的半透明圆。最终结果是一个径向渐变效果。圆叠加得越更多,原先所画的圆的透明度会越低。通过增加循环次数,画更多的圆,从中心到边缘部分,背景图会呈现逐渐消失的效果。

function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
// 画背景
ctx.fillStyle = '#FD0';
ctx.fillRect(0,0,75,75);
ctx.fillStyle = '#6C0';
ctx.fillRect(75,0,75,75);
ctx.fillStyle = '#09F';
ctx.fillRect(0,75,75,75);
ctx.fillStyle = '#F30';
ctx.fillRect(75,75,75,75);
ctx.fillStyle = '#FFF';

// 设置透明度值
ctx.globalAlpha = 0.2;

// 画半透明圆
for (var i=0;i<7;i++){
ctx.beginPath();
ctx.arc(75,75,10+10*i,0,Math.PI*2,true);
ctx.fill();
}
}

线型 Line styles

// 设置线条宽度。
lineWidth = value

// 设置线条末端样式。
lineCap = type

// 设定线条与线条间接合处的样式。
lineJoin = type

// 限制当两条线相交时交接处最大长度;所谓交接处长度(斜接长度)是指线条交接处内角顶点到外角顶点的长度。
miterLimit = value

// 返回一个包含当前虚线样式,长度为非负偶数的数组。
getLineDash()

// 设置当前虚线样式。
setLineDash(segments)

// 设置虚线样式的起始偏移量。
lineDashOffset = value

lineWidth 属性的例子

function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
for (var i = 0; i < 10; i++){
ctx.lineWidth = 1+i;
ctx.beginPath();
ctx.moveTo(5+i*14,5);
ctx.lineTo(5+i*14,140);
ctx.stroke();
}
}

lineCap 属性的例子

属性 lineCap 的值决定了线段端点显示的样子。它可以为下面的三种的其中之一:butt,round 和 square。默认是 butt

function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
var lineCap = ['butt','round','square'];

// 创建路径
ctx.strokeStyle = '#09f';
ctx.beginPath();
ctx.moveTo(10,10);
ctx.lineTo(140,10);
ctx.moveTo(10,140);
ctx.lineTo(140,140);
ctx.stroke();

// 画线条
ctx.strokeStyle = 'black';
for (var i=0;i<lineCap.length;i++){
ctx.lineWidth = 15;
ctx.lineCap = lineCap[i];
ctx.beginPath();
ctx.moveTo(25+i*50,10);
ctx.lineTo(25+i*50,140);
ctx.stroke();
}
}

lineJoin 属性的例子

lineJoin 的属性值决定了图形中两线段 连接处所 显示的样子。它可以是这三种之一:round, bevel 和 miter。默认是 miter

function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
var lineJoin = ['round', 'bevel', 'miter'];
ctx.lineWidth = 10;
for (var i = 0; i < lineJoin.length; i++) {
ctx.lineJoin = lineJoin[i];
ctx.beginPath();
ctx.moveTo(-5, 5 + i * 40);
ctx.lineTo(35, 45 + i * 40);
ctx.lineTo(75, 5 + i * 40);
ctx.lineTo(115, 45 + i * 40);
ctx.lineTo(155, 5 + i * 40);
ctx.stroke();
}
}

使用虚线

用 setLineDash 方法和 lineDashOffset 属性来制定虚线样式。 setLineDash 方法接受一个数组,来指定线段与间隙的交替;lineDashOffset 属性设置起始偏移量。

var ctx = document.getElementById('canvas').getContext('2d');
var offset = 0;

function draw() {
ctx.clearRect(0,0, canvas.width, canvas.height);
ctx.setLineDash([4, 2]);
ctx.lineDashOffset = -offset;
ctx.strokeRect(10,10, 100, 100);
}

function march() {
offset++;
if (offset > 16) {
offset = 0;
}
draw();
setTimeout(march, 20); // 这里设置一个动画
}

march();

使用图片

引入图像到 canvas 里需要以下两步基本操作:

  1. 获得一个指向 HTMLImageElement 的对象或者另一个 canvas 元素的引用作为源,也可以通过提供一个URL的方式来使用图片
  2. 使用 drawImage() 函数将图片绘制到画布上

获得需要绘制的图片

// 这些图片是由Image()函数构造出来的,或者任何的<img>元素
HTMLImageElement

// 用一个HTML的 <video>元素作为你的图片源,可以从视频中抓取当前帧作为一个图像
HTMLVideoElement


// 可以使用另一个 <canvas> 元素作为你的图片源。
HTMLCanvasElement

// 这是一个高性能的位图,可以低延迟地绘制,它可以从上述的所有源以及其它几种源中生成。
ImageBitmap

使用例

var img = new Image();   // 创建一个<img>元素
img.src = 'myImage.png'; // 设置图片源地址

Image() 函数将会创建一个新的HTMLImageElement实例,它的功能等价于 document.createElement('img')

var myImage = new Image(100, 200);
myImage.src = 'picture.jpg';
document.body.appendChild(myImage);

上面的代码相当于在 <body> 中定义了下面的HTML:

<img width="100" height="200" src="picture.jpg">

若调用 drawImage 时,图片没装载完,那什么都不会发生(在一些旧的浏览器中可能会抛出异常)。因此你应该用load事件来保证不会在加载完毕之前使用这个图片:

var img = new Image();   // 创建img元素
img.onload = function(){
// 执行drawImage语句
}

img.src = 'myImage.png'; // 设置图片源地址

还可以通过 data:url 方式来引用图像。Data urls 允许用一串 Base64 编码的字符串的方式来定义一个图片。

img.src = 'data:image/gif;base64,R0lGODlhCwALAIAAAAAA3pn/ZiH5BAEAAAEALAAAAAALAAsAAAIUhA+hkcuO4lmNVindo7qyrIXiGBYAOw==';

绘制图片

一旦获得了源图对象,就可以使用 drawImage 方法将它渲染到 canvas 里

最基本的用法

// 其中 image 是 image 或者 canvas 对象,x 和 y 是其在目标 canvas 里的起始坐标。
drawImage(image, x, y)

下面一个例子用一个外部图像作为一线性图的背景。

function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
var img = new Image();
img.onload = function(){
ctx.drawImage(img,0,0);
ctx.beginPath();
ctx.moveTo(30,96);
ctx.lineTo(70,66);
ctx.lineTo(103,76);
ctx.lineTo(170,15);
ctx.stroke();
}
img.src = 'https://image.alsritter.icu/files/5395/backdrop.png';
}

这个线性图是外部图片

缩放 Scaling

drawImage 方法的又一变种是增加了两个用于控制图像在 canvas 中缩放的参数。

// 这个方法多了2个参数:width 和 height,这两个参数用来控制 当向canvas画入时应该缩放的大小
drawImage(image, x, y, width, height)

例子:平铺图像

用一张图片像背景一样在 canvas 中以重复平铺开来。实现起来也很简单,只需要循环铺开经过缩放的图片即可。第一层 for 循环是做行重复,第二层是做列重复的。图像大小被缩放至原来的三分之一

function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
var img = new Image();
img.onload = function(){
for (var i=0;i<4;i++){
for (var j=0;j<3;j++){
ctx.drawImage(img,j*50,i*38,50,38);
}
}
};
img.src = 'https://image.alsritter.icu/files/5397/rhino.jpg';
}

原图这个样子

缩放后平铺的图这个样子

切片 Slicing

drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

第一个参数和其它的是相同的,都是一个图像或者另一个 canvas 的引用。

其它8个参数前 4 个是定义图像源的切片位置和大小,后 4 个则是定义切片的目标显示位置和大小。

给犀牛头做个切片特写,然后合成到一个相框里面去。这里直接将图像插入到 HTML 里面来获取图片,然后通过 CSS 隐藏它(display:none)

<html>
<body onload="draw();">
<canvas id="canvas" width="150" height="150"></canvas>
<div style="display:none;">
<img id="source" src="https://image.alsritter.icu/files/5397/rhino.jpg" width="300" height="227">
<img id="frame" src="https://image.alsritter.icu/files/242/Canvas_picture_frame.png" width="132" height="150">
</div>
</body>
</html>

function draw() {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

// Draw slice
ctx.drawImage(document.getElementById('source'),
33,71,104,124,21,20,87,104);

// Draw frame
ctx.drawImage(document.getElementById('frame'),0,0);
}

基本动画

通过以下的步骤来画出一帧:

1、清空 canvas 除非接下来要画的内容会完全充满 canvas (例如背景图),否则你需要清空所有。最简单的做法就是用 clearRect 方法。

2、保存 canvas 状态 如果你要改变一些会改变 canvas 状态的设置(样式,变形之类的),又要在每画一帧之时都是原始状态的话,你需要先保存一下。

3、绘制动画图形(animated shapes) 这一步才是重绘动画帧。

4、恢复 canvas 状态 如果已经保存了 canvas 的状态,可以先恢复它,然后重绘下一帧。

在 canvas 上绘制内容是用 canvas 提供的或者自定义的方法,而通常,我们仅仅在脚本执行结束后才能看见结果,比如说,在 for 循环里面做完成动画是不太可能的。

使用 setTimeout 定时执行

因此, 为了实现动画,我们需要一些可以定时执行重绘的方法。有两种方法可以实现这样的动画操控。首先可以通过 setTimeout 方法来控制在设定的时间点上执行重绘。

var ctx = document.getElementById('canvas').getContext('2d');
var offset = 0;

function draw() {
ctx.clearRect(0,0, canvas.width, canvas.height);
ctx.setLineDash([4, 2]);
ctx.lineDashOffset = -offset;
ctx.strokeRect(10,10, 100, 100);
}

function march() {
offset++;
if (offset > 16) {
offset = 0;
}
draw();
setTimeout(march, 20); // 这里设置一个动画
}

march();

使用 requestAnimationFrame 绘制动画

与 setTimeout 相比,requestAnimationFrame 最大的优势是由系统来决定回调函数的执行时机。具体一点讲,如果屏幕刷新率是60Hz,那么回调函数就每 16.7ms 被执行一次,如果刷新率是 75Hz,那么这个时间间隔就变成了 1000/75=13.3ms,换句话说就是,requestAnimationFrame 的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。

var progress = 0;
//回调函数
function render() {
progress += 1; //修改图像的位置
if (progress < 100) { //在动画没有结束前,递归渲染
window.requestAnimationFrame(render);
}
}
//第一帧渲染
window.requestAnimationFrame(render);